package roborally;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;
import roborally.Exceptions.IllegalDestinationException;


/**
 * A class of repairkits, that extends the class carryable. Repairkits have an energy, maxEnergy and a weight.
 * 
 * @invar  	| Energy.isValidEnergyAmount(this.getEnergy(),this.getMaxEnergy())
 * @invar  	| Energy.isValidMaxEnergyAmount(this.getMaxEnergy(),standardMaxEnergyAmount)
 * 
 * @version  2.90
 * @author   Michiel Ruelens & Dieter Naten
 */
public class RepairKit extends Carryable {
	
	private Energy energy;
	private Energy maxEnergy;
	private final EnergyUnit standardEnergyUnit = EnergyUnit.WATTSECOND;
	private final double standardMaxEnergyAmount = Double.MAX_VALUE;
	//Inherits 'boolean isBeingCarried'
	//Inherits 'long x'
	//Inherits 'long y'
	//Inherits 'Board board'
	//Inherits 'String type'

	/**
     * Initialize the new repairkit with given initialEnergy and weight.
     * 
     * @param 	repairAmount
     * 		   	The repairAmount energy of the new repairkit.
     * @param  	weight
     * 		   	The weight of the new repairkit.
     * @pre   	| Energy.isValidEnergyAmount(initialEnergy, standardEnergyUnit, getMaxEnergy())
     * @effect  | setMaxEnergy(new Energy(standardMaxEnergyAmount,standardEnergyUnit))
     * @effect  | setEnergy(new Energy(repairAmount,standardEnergyUnit))
     * @effect 	| setPlaceableType(PlaceableType.SURPRISEBOX)
     */
	@Raw
	public RepairKit(double repairAmount, int weight){
		super(weight); //Sets terminated status to false
		setPlaceableType(PlaceableType.REPAIRKIT);
		setMaxEnergy(new Energy(standardMaxEnergyAmount,standardEnergyUnit));
		assert(Energy.isValidEnergyAmount(repairAmount, standardEnergyUnit, getMaxEnergy()));
		setEnergy(new Energy(repairAmount,standardEnergyUnit));
	}
	
	/**
	 * This method puts the current repairkit on a board.
	 * 
	 * @param board
	 * 		  The board on which the repairkit should be placed
	 * @param x
	 * 		  The x-coordinate on which the repairkit should be placed
	 * @param y
	 * 		  The y-coordinate on which the repairkit should be placed
	 * @effect | putPlaceable(board,x,y)
	 */
	public void putRepairKit(Board board, long x, long y){
		putPlaceable(board,x,y);
	}
	
	/**
	 * Return the current energy of this repairkit.
	 */
	@Basic
	public Energy getEnergy() {
		return energy;
	}

	/**
	 * This method sets the energy of a repairkit to the given energy.
	 * 
	 * @param 	energy
	 * 			The energy this repairkit is to be given.
	 * @pre		| Energy.isValidEnergyAmount(energy.getNumeral(),energy.getUnit(),getMaxEnergy())
	 * @post	| (new this).getEnergy() == energy
	 */	
	public void setEnergy(Energy energy) {
		assert(Energy.isValidEnergyAmount(energy.getNumeral(),energy.getUnit(),getMaxEnergy()));
		this.energy = energy;
	}
	
	/**
	 * Return the current maxEnergy of this repairkit.
	 */
	@Basic
	public Energy getMaxEnergy() {
		return maxEnergy;
	}

	/**
	 * This method sets the maxEnergy of a repairkit to the given maxEnergy.
	 * 
	 * @param 	maxEnergy
	 * 			The maxEnergy this repairkit is to be given.
	 * @pre		| Energy.isValidMaxEnergyAmount(maxEnergy.getNumeral(), maxEnergy.getUnit(), standardMaxEnergyAmount)
	 * @post	| (new this).getMaxEnergy() == maxEnergy
	 */
	public void setMaxEnergy(Energy maxEnergy) {
		assert(Energy.isValidMaxEnergyAmount(maxEnergy, standardMaxEnergyAmount));
		this.maxEnergy = maxEnergy;
	}

	/**
	 * Return the standard Energy unit of this repairkit.
	 */
	@Basic
	public EnergyUnit getStandardEnergyUnit() {
		return standardEnergyUnit;
	}

	/**
	 * This method is executed when a repairkit is hit. The energy of the repairkit is increased, if possible.
	 *
	 * @effect 	| currentEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()
	 * 			| if (currentEnergy+500 <= this.standardMaxEnergyAmount) 
	 * 			|	{this.setEnergy(new Energy(currentEnergy +500,EnergyUnit.WATTSECOND))}
	 * 			| else if(currentEnergy+500 < 0)
	 * 			| 	{this.setEnergy(new Energy(this.standardMaxEnergyAmount,EnergyUnit.WATTSECOND))}
	 */	
	@Override
	public void hit(){
		double currentEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral();
		if(currentEnergy+500 <= this.standardMaxEnergyAmount){
			Energy newEnergy1 = new Energy(currentEnergy +500,EnergyUnit.WATTSECOND);
			this.setEnergy(newEnergy1);
		}
		//WE TEST SMALLER THAN ZERO, BECAUSE IF YOU ADD SOMETHING TO THE MAX VALUE INTEGER, IT WILL BECOME NEGATIVE MAX INTEGER
		else if(currentEnergy+500 < 0){
			Energy newEnergy2 = new Energy(this.standardMaxEnergyAmount,EnergyUnit.WATTSECOND);
			this.setEnergy(newEnergy2);
		}
		System.out.println("A repairkit has been hit, it's energy has increased with 500 Ws!");
		System.out.println("It now has" + this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral() + " Ws!");
	}
	
	/**
	 * Method executed when a robot uses a repairkit.
	 * 
	 * @param   robot
	 * 			The robot that uses the current repairkit
	 * @pre		| !this.isTerminated()
	 * @pre		| robot.getAllPossessions().contains(this)
	 * @effect 	| currentAddEnergy = robot.getMaxEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()
	 * 			|						+ (this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()/2)
	 * 			| if (currentAddEnergy<= robot.maximumMaxEnergyAmountInWS) 
	 * 			|	{	 robot.setMaxEnergy(new Energy(currentAddEnergy,EnergyUnit.WATTSECOND))
	 *			|		 robot.removeCarryableFromPossessions(this)
	 *			|		 this.terminate()}
	 * 			| else if(currentAddEnergy>robot.maximumMaxEnergyAmountInWS)
	 * 			| 	{	 robot.setMaxEnergy(new Energy(robot.maximumMaxEnergyAmountInWS,robot.getMaxEnergy().getUnit()))
	 * 			|	 	 this.setEnergy(new Energy((currentAddEnergy-robot.maximumMaxEnergyAmountInWS)*2
	 * 			|									,EnergyUnit.WATTSECOND).toUnit(this.getEnergy().getUnit()))}
	 */
	public void useCarryable(Robot robot){
		assert(!this.isTerminated());
		assert(robot.getAllPossessions().contains(this));
		EnergyUnit originalRepairKitUnit = this.getEnergy().getUnit();
		EnergyUnit originalRobotMaxEnergyUnit = robot.getMaxEnergy().getUnit();
		Energy repairkitEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND);
		Energy robotMaxEnergy = robot.getMaxEnergy().toUnit(EnergyUnit.WATTSECOND);	
		double currentAddEnergy = robotMaxEnergy.getNumeral()+(repairkitEnergy.getNumeral()/2);
			//Repairkit is already depleted, should never get here
			if(this.getEnergy().getNumeral()==0){
				robot.removeCarryableFromPossessions(this);
				this.terminate();
				System.out.println("That repairkit is out of energy.");
			}
			//Simple maxEnergy increase, terminate repairkit
			if(currentAddEnergy<= robot.standardMaxEnergyAmountInWS){
				Energy newMaxEnergy = new Energy(currentAddEnergy,EnergyUnit.WATTSECOND);
				robot.setMaxEnergy(newMaxEnergy);
				robot.removeCarryableFromPossessions(this);
				this.terminate();
				System.out.println("Robot <"+robot+"> got a new higher maxEnergy: "+ robot.getMaxEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()+" Ws. The repairkit is removed from the possessions and destroyed.");
			}
			//Repairkit has so many energy that maxEnergy would exceed 20000Ws
			else if(currentAddEnergy>robot.standardMaxEnergyAmountInWS){
				double excessAmount = currentAddEnergy-robot.standardMaxEnergyAmountInWS; 
				Energy newMaxEnergy = new Energy(robot.standardMaxEnergyAmountInWS,originalRobotMaxEnergyUnit);
				robot.setMaxEnergy(newMaxEnergy);
				this.setEnergy(new Energy(excessAmount*2,EnergyUnit.WATTSECOND).toUnit(originalRepairKitUnit));
				System.out.println("Robot <"+robot+"> has got it's maxEnergy set to it's maximum possible value.");
				System.out.println("The repairkit has "+excessAmount*2+" Ws of energy left.");
			}
	}
}
